iT邦幫忙

2024 iThome 鐵人賽

DAY 2
2
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 2

資安這條路:Day 2 了解使用者輸入與輸出與前端關係

  • 分享至 

  • xImage
  •  

前言

今天的更新內容主要是建立一個簡單的網頁伺服器,並透過表單與API來接受使用者的輸入。

我們將一步一步地講解每個部分的設計理念,以及可能存在的安全問題,特別是如何防範 HTML注入CSS注入、和 JavaScript注入 等漏洞。

為什麼這樣設計

  • 簡單直觀:使用最基本的 HTML、CSS、JavaScript 和 Node.js,適合新手學習。
  • 強調互動:透過實際操作,讓學習者理解前後端的交互作用。
  • 引入安全概念:為後續的安全教學(如注入攻擊)打下基礎。

程式碼內容介紹

1. docker-compose.yml

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./web:/usr/src/app
      - /usr/src/app/node_modules
    command: sh -c "npm install && npm start"
  mongo:
    image: mongo:latest

說明

  • 增加 command: sh -c "npm install && npm start" 的原因:自動化根據相依性的套件進行安裝,避免因為缺少套件而導致應用程式無法正常執行。
  • sh -c:使用 Shell 執行多個指令。
  • &&:確保前一個指令(npm install)成功執行後,才會執行下一個命令(npm start)。如果安裝失敗,應用程式不會啟動,幫助開發者及早發現問題。

資安風險

  • 執行 npm install 會根據 package.json 安裝所有的相依套件,有一些相依套件根據 postinstall 這些腳本會自動執行,如果相依套件的內容被「惡意竄改」可能包含有害腳本,若在 Docker 容器中執行,可能會導致資安漏洞。
  • Docker 容器預設以 root 進行執行,雖然容器可能有進行隔離,但是仍可能有「容器逃逸漏洞」。

2. web/public/index.html

<html>
<head>
  <meta charset="UTF-8">
  <title>歡迎來到測試網頁</title>
</head>
<body>
  <h1>歡迎來到測試網頁</h1>
  <button onclick="fetchMessage()">從 API 取得資訊</button>
  <div id="message"></div>
  進入 <a href="/form">表單頁面</a>

  <script>
    function fetchMessage() {
      fetch('/api/hello')
        .then(response => response.json())
        .then(data => {
          document.getElementById('message').innerText = data.message;
        });
    }
  </script>
</body>
</html>

說明

  • 新增第十行:進入 <a href="/form">表單頁面</a> 連結到表單頁面,讓使用者能夠前往 /form 頁面,進行 GET 和 POST 操作。

3. web/public/form.html

<html>
<head>
  <meta charset="UTF-8">
  <title>表單頁面</title>
</head>
<body>
  <h1>表單操作</h1>

  <h2>GET 請求</h2>
  <!-- 搜尋表單 -->
  <form action="/search" method="get">
    <input type="text" name="keyword" placeholder="請輸入關鍵字">
    <input type="submit" value="搜尋">
  </form>

  <h2>POST 表單</h2>
  <!-- 登入表單 -->
  <form action="/submit" method="post">
    <input type="text" name="username" placeholder="帳號">
    <input type="password" name="password" placeholder="密碼">
    <input type="submit" value="送出">
  </form>
</body>
</html>

說明

  • 建立表單 HTML 分成兩個 Form 並在 method 使用不同方法,以及 action 使用不同目的地。
    • GET 請求表單:讓使用者輸入關鍵字,透過GET方式送出。
    • POST 請求表單模擬登入功能,透過POST方式送出帳號和密碼,模擬登入表示尚未串接資料庫和其他驗證帳號密碼的方法。

4. web/server.js(1/3)


// 設定 form
app.get('/form', (req, res) => {
  // 當存取 form 路徑時,請求 form.html 檔案
  res.sendFile(path.join(__dirname, 'public', 'form.html'));
});

說明

使用 Express.js 設定一個 GET 路由,當使用者存取 /form 路徑時,伺服器會傳送位於 public 資料夾中的 form.html 檔案。res.sendFile() 方法用於傳送指定路徑的檔案作為回應。

資安風險

  1. 檔案暴露:如果 form.html 包含敏感資訊,直接暴露給所有使用者可能帶來風險。應確保該檔案不含機密資料,或實施適當的存取控制。
  2. 缺乏存取控制:任何人都可以存取 /form 路徑。如果該表單僅適用於特定使用者,應該加入身份驗證或授權機制。

4. web/server.js(2/3)

// 設定搜尋路由
app.get('/search', (req, res) => {
  // 取得查詢字串參數
  const keyword = req.query.keyword;
  // 顯示查詢字串參數
  res.send(`你搜尋的關鍵字是:${keyword}`);
});

說明

這段程式碼設定一個 GET 路由 /search。當使用者在查詢字串中提供 keyword 參數時,伺服器會取得該參數並在回應中顯示。例如,存取 /search?keyword=測試,伺服器會回應「你搜尋的關鍵字是:測試」。

資安風險

  1. 反射型跨站腳本攻擊(Reflected XSS):直接將使用者輸入的 keyword 顯示在回應中,且未經任何過濾或轉義,攻擊者可以藉此插入惡意腳本。例如,存取 /search?keyword=<script>alert('XSS')</script>,可能導致使用者瀏覽器執行惡意腳本。

  2. 資訊洩漏:如果回應中包含敏感資訊,可能會被未經授權的第三方攔截或利用。

4. web/server.js(3/3)

// 使用 express.json() 來解析 JSON 格式的請求
app.use(express.json());

// 使用 express.urlencoded() 來解析 URL 編碼的請求
app.use(express.urlencoded({ extended: true }));

// 設定表單提交路由
app.post('/submit', (req, res) => {
  // 取得 POST 請求的參數
  const { username, password } = req.body;
  // 顯示 POST 請求的參數
  res.send(`你的帳號是 ${username},密碼是 ${password}`);
});

說明

這段程式碼使用中介軟體 express.json()express.urlencoded() 來解析請求主體中的 JSON 和 URL 編碼資料。然後,設定了一個 POST 路由 /submit,從請求主體中解構取得 usernamepassword,並在回應中顯示這些資訊。

資安風險

  1. 敏感資訊暴露:直接在回應中顯示使用者的帳號和密碼,可能導致敏感資訊被攔截或在日誌中被記錄,增加被未授權存取的風險。
  2. 缺乏輸入驗證:未對 usernamepassword 進行任何驗證,可能使系統易受 SQL 注入或其他注入式攻擊的影響。
  3. 跨站腳本攻擊(XSS):如果 usernamepassword 包含惡意腳本,且未經過濾,可能導致 XSS 攻擊。
  4. 不安全的傳輸:如果未使用 HTTPS,加密的敏感資訊可能在傳輸過程中被攔截。
  5. 密碼處理不當:密碼應該經過雜湊(如 bcrypt)並安全儲存,且絕不應該在回應中以明文形式回傳。

操作介紹

  1. 啟動伺服器:在終端機中執行 docker-compose up,啟動服務。
  2. 瀏覽主頁:在瀏覽器中打開 http://localhost:3000,看到主頁內容。
  3. 進入表單頁面:點擊連結進入 /form,進行GET和POST請求的測試。
  4. 測試搜尋功能:在搜尋框中輸入關鍵字,提交後看到回應內容。
  5. 測試登入功能:輸入帳號和密碼,提交後看到回應內容。

資安介紹

在接受使用者輸入時,如果未對輸入內容進行適當的驗證和過濾,可能會導致 HTML注入CSS注入、和 JavaScript注入 等安全漏洞。

例如:

  • HTML注入:攻擊者輸入惡意的HTML標籤,影響網頁結構。
  • CSS注入:插入惡意的CSS,改變網頁的外觀或隱藏元素。
  • JavaScript注入:執行惡意的JavaScript程式碼,可能竊取使用者資訊。

步驟拆解

步驟一:設定環境

  • 安裝 DockerDocker-composegit (如果尚未安裝)。
  • 下載專案的程式碼,或是用
    • git checkout db6597ae93f1d0e19879c69920021346c3850be7 切換到該 Commit。

sudo apt install git
image

git clone https://github.com/fei3363/ithelp_web_security_2024.git
image

cd ithelp_web_security_2024/

git checkout db6597ae93f1d0e19879c69920021346c3850be7 切換到該 Commit
image

步驟二:理解主頁 (index.html)

  • 主頁提供一個 <a> 標籤,可以連到 href 指定的網頁。

image

步驟三:理解表單頁面 (form.html)

  • 包含兩個表單:
    • GET 請求表單:用於搜尋功能,提交到 /search
    • POST 請求表單:模擬登入功能,提交到 /submit

目標

  • GET 和 POST 請求的差異。
  • 體驗使用者輸入如何傳遞到後端。

GET 方法

image

http://nodelab.feifei.tw/search?keyword=%E6%B8%AC%E8%A9%A6

http://nodelab.feifei.tw/search?keyword=%E6%B8%AC%E8%A9%A6

  1. Protocol: http://

  2. Domain: nodelab.feifei.tw

  3. Path: /search

  4. Query: ?keyword=%E6%B8%AC%E8%A9%A6

  5. Protocol(協定): http://

    • 這表示使用的是 HTTP(超文本傳輸協定)。
    • 注意:這不是 HTTPS,意味著資料傳輸是不加密的,可能存在安全風險。
  6. Domain(網域): nodelab.feifei.tw

    • 這是網站的域名。
    • nodelab 是子域名,feifei.tw 是主域名。
  7. Path(路徑): /search

    • 正在存取網站的搜尋功能。
  8. Query(查詢): ?keyword=%E6%B8%AC%E8%A9%A6

    • 這是 URL 的查詢部分,包含了傳遞給搜尋功能的參數。
    • keyword 是參數名稱。
    • %E6%B8%AC%E8%A9%A6 是經過 URL 編碼的參數值。

特別說明查詢部分:

  • %E6%B8%AC%E8%A9%A6 是 URL 編碼後的中文字符「測試」。
  • URL 編碼將非 ASCII 字符轉換為可以在 URL 中安全傳輸的格式。
  • 解碼後,實際的搜尋關鍵字是「測試」。
安全性考慮
  1. 使用 HTTP 而非 HTTPS 可能導致資料在傳輸過程中被竊聽或篡改。
  2. 搜尋關鍵字直接顯示在 URL 中,可能會被記錄在瀏覽歷史或伺服器日誌中,這可能會引發隱私問題。
  3. 若搜尋功能沒有妥善處理使用者輸入,可能會出現 SQL 注入或 XSS(跨站腳本)等安全漏洞。

POST 方法

image

POST 是 HTTP 協議中常用的請求方法之一,主要用於向伺服器提交資料。與 GET 方法不同,POST 方法將資料放在請求主體(body)中,而不是 URL 中。

POST 請求範例

POST 請求的主要特點
  1. 資料在請求主體中: 資料不會顯示在 URL 中,提高了一定的安全性。
  2. 資料大小限制較寬鬆: 相比 GET 方法,POST 可以發送更大量的資料。
  3. 不會被緩存: POST 請求通常不會被瀏覽器緩存。
請求封包結構
  1. Content-Type: application/x-www-form-urlencoded

    • POST 請求內容類型之一。
    • 表示資料將以 key-value 的形式編碼,類似於 URL 參數。
  2. Payload (請求主體):

    username=admin&password=password
    
    • 這是實際發送的資料。
    • 格式為 key-value ,用 & 分隔。
安全性考慮
  1. 敏感資料傳輸: 雖然資料不在 URL 中,但在未使用 HTTPS 的情況下,仍可能被攔截。
  2. 密碼處理: 範例中的密碼以明文形式發送,這是不安全的做法。
  3. 伺服器端驗證: 伺服器必須對接收到的資料進行嚴格驗證,以防止注入攻擊等安全問題。
  4. CSRF 防護: POST 請求可能需要額外的 CSRF (跨站請求偽造) 防護措施。

步驟四:理解伺服器程式 (server.js)

  • 使用 Express.js 建立伺服器,處理各種路由請求。
  • 使用中介軟體解析請求內容。
  • 回應使用者的請求並將輸入內容回傳給使用者。

練習內容

1. HTML、CSS、JavaScript 基礎

  • HTML:理解表單元素、標籤結構。
  • CSS:學習基本的樣式設定。
  • JavaScript:熟悉 JS 事件處理、DOM 操作和 Fetch API。

2. 實作注入攻擊練習

目標

  • 瞭解當未對使用者輸入進行驗證時,可能產生的安全漏洞。

練習環境

  • 網址:http://nodelab.feifei.tw/form
  • 頁面包含兩個表單,如圖所示:

表單頁面

1. HTML 注入

輸入: <h1>這是標題</h1>

結果: 所輸入的內容被解析成 HTML

HTML 注入結果

解釋:

  • 伺服器直接將使用者輸入作為 HTML 內容返回,沒有進行任何過濾或轉義。
  • 這允許攻擊者注入任意 HTML 內容,可能導致頁面結構改變或引入惡意內容。

2. CSS 注入

輸入: <style>body { background-color: red; }</style>

結果: 頁面背景變為紅色

CSS 注入結果

解釋:

  • 注入的 CSS 程式碼被瀏覽器執行,改變了頁面的樣式。
  • 攻擊者可以通過 CSS 注入改變頁面外觀,甚至在某些情況下竊取敏感信息。

3. JavaScript 注入

輸入: <script>alert('你被攻擊了!');</script>

結果: 彈出警告框

JavaScript 注入結果

解釋:

  • 注入的 JavaScript 程式碼被瀏覽器執行,顯示了一個警告框。
  • 這種類型的注入最為危險,因為它允許攻擊者執行任意 JavaScript 程式碼。
  • 攻擊者可以利用此漏洞竊取使用者資料、修改頁面內容、重新導向使用者等。

如何防範注入攻擊

  • 輸入驗證與過濾:在後端對使用者輸入進行嚴格的驗證和過濾。
  • 轉義特殊字符:在輸出到前端前,轉義HTML特殊字符。
  • 使用模板引擎:採用安全的模板引擎,自動處理轉義。

轉議

// 使用 he 
const he = require('he');

app.post('/submit', (req, res) => {
  const { username, password } = req.body;
  res.send(`你的帳號是 ${he.encode(username)},密碼是 ${he.encode(password)}`);
});
// 使用 sanitize-html 
const sanitizeHtml = require('sanitize-html');

app.post('/submit', (req, res) => {
  const { username, password } = req.body;
  res.send(`你的帳號是 ${sanitizeHtml(username)},密碼是 ${sanitizeHtml(password)}`);
});
// 使用 DOMPurify  (需要在 Node.js 環境中使用 jsdom)
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

app.post('/submit', (req, res) => {
  const { username, password } = req.body;
  res.send(`你的帳號是 ${DOMPurify.sanitize(username)},密碼是 ${DOMPurify.sanitize(password)}`);
});
  1. he

    • 安裝: npm install he
    • 用途: 主要用於HTML實體的編碼和解碼
    • 優點: 輕量級,專注於實體編碼
    • 缺點: 功能相對簡單,不提供進階的HTML清理
  2. sanitize-html

    • 安裝: npm install sanitize-html
    • 用途: HTML清理,可以移除不安全的標籤和屬性
    • 優點:
      • 可配置性高,可以自定義允許的標籤和屬性
      • 適合處理富文本輸入
    • 缺點: 對於簡單的轉義可能過於複雜
  3. DOMPurify

    • 安裝: npm install dompurify jsdom
    • 用途: 全面的HTML、MathML 和 SVG 清理
    • 優點:
      • 非常強大,可以處理複雜的XSS攻擊場景
      • 在瀏覽器和 Node.js 環境中都可使用
    • 缺點:
      • 在 Node.js 中需要額外設定 (如使用 jsdom)
      • 對於簡單的轉義可能過於肥大

結論

透過本次的教學,我們了解了使用者輸入與輸出的基本操作,以及可能存在的安全問題。在未來的學習中,我們將深入探討如何加強應用程式的安全性,防範各種潛在的攻擊。

你的下一步

  1. 設定測試環境
    • Clone 我們的範例專案:git clone https://github.com/fei3363/ithelp_web_security_2024.git
    • 切換到特定版本:git checkout db6597ae93f1d0e19879c69920021346c3850be7
    • 按照 README 指示啟動專案
  2. HTML 注入測試
    • 嘗試在搜尋框中輸入 <h1>這是一個測試</h1>
    • 觀察結果並思考潛在風險
  3. JavaScript 注入嘗試
    • 在表單中輸入 <script>alert('XSS 測試')</script>
    • 分析系統對此輸入的反應
  4. 提出改進建議
    • 基於發現,列出至少三個提高系統安全性的建議

💡 挑戰自己

完成上述任務後,嘗試實施一些基本的安全措施。例如:

  • 在伺服器端增加輸入驗證
  • 實現簡單的 XSS 防護機制

參考資料


上一篇
資安這條路:Day 1 建立環境與認識網站架構
下一篇
資安這條路:Day 3 深入探索 DNS:從基礎概念到資安風險
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言